﻿using System.Collections.Generic;
using System.Linq;

namespace KxParser.Parser.LR
{
    public class StateEx
    {
        public int Id { get; set; }
        public List<ProductionItemEx> ProductionItems = new List<ProductionItemEx>();

        public StateEx(int id)
        {
            Id = id;
        }

        public string[] GetNextPaths()
        {
            return
                ProductionItems.Select(productionItem => productionItem.GetNextRuleItem()).Where(
                    nextRuleItem => !string.IsNullOrEmpty(nextRuleItem) && !nextRuleItem.Equals(KxParserGlobal.Ender)).Distinct()
                    .ToArray();
        }


        /// <summary>
        /// Creates closure states for current states. If closure states are already created, method does nothing.
        /// </summary>
        /// <see cref="http://www.cs.virginia.edu/~cs415/reading/lr-example.pdf"/>
        public void CreateClosure()
        {
            var alreadyAddedNonTerminals = new List<string>();

            var queue = new Queue<ProductionItemEx>();
            foreach (var productionItem in ProductionItems)
            {
                ProductionItemEx newPid = new ProductionItemEx(productionItem);
                queue.Enqueue(newPid);
            }

            while (queue.Count > 0)
            {
                var currProductioItem = queue.Dequeue();

                string nextRuleItem = currProductioItem.GetNextRuleItem();
                if (nextRuleItem != null && KxParserGlobal.IsNonTerminal(nextRuleItem) &&
                    !alreadyAddedNonTerminals.Contains(nextRuleItem))
                {
                    alreadyAddedNonTerminals.Add(nextRuleItem);
                    var additionalProductionItems = getProductionItemsByName(nextRuleItem);

                    foreach (var additionalProductionItem in additionalProductionItems)
                    {
                        var newPid = new ProductionItemEx(additionalProductionItem);
                        ProductionItems.Add(newPid);
                        queue.Enqueue(newPid);
                    }
                }
            }

            foreach (var productionItem in ProductionItems)
            {
                productionItem.CurrentState = this;
            }
        }

        ProductionItemEx[] getProductionItemsByName(string name)
        {
            return KxParserGlobal.ProductionItemsEx.Where(productionItem => productionItem.Name.Equals(name)).ToArray();
        }

        public override bool Equals(object obj)
        {
            if(typeof(StateEx).IsAssignableFrom(obj.GetType()))
            {
                StateEx s2 = (StateEx) obj;

                if(ProductionItems.Count != s2.ProductionItems.Count)
                    return false;

                foreach (ProductionItemEx productionItem1 in ProductionItems)
                {
                    bool found = false;
                    foreach (ProductionItemEx productionItem2 in s2.ProductionItems)
                    {
                        if(productionItem1.Equals(productionItem2))
                        {
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                        return false;
                }
                return true;
            }
            return false;
        }


        public override string ToString()
        {
            return string.Format("State {0} - {1} prod. items", Id, ProductionItems.Count);
        }
    }
}
